/*
==========================================================
DX490a - Summer 2010
Instructor: Stelios Manousakis
==========================================================
Class 8.1:
Interfacing 4: Feature extraction in SuperCollider
Contents:
• About
• Feature extraction in SC
- Amplitude
- Pitch
- Time-related
- timbre
==========================================================
*/
// ================= ABOUT =================
/*
• Psychological dimensions of sound:
- loudness
- pitch
- duration
- timbre
• Data you can get:
- continuous: description of a particular characteristic in time.
- switches/triggers: detection of specific behaviors, energy in specific areas, etc
• Analysis domains:
- analysis of the psychological dimensions of sound (pshycoacoustic analysis / feature extraction)
- analysis of organizational qualities through gestalt-driven analysis (cognitive analysis / machine listening)
> Our brains attempt to make sense of our noisy world, compressing the torrent of incoming sensual data by finding symmetry, order, simplicity and patterns. A set of laws has been postulated by Gestalt psychologists to explain different strategies our brain uses to group objects together:
proximity, closure, similarity, continuity, symmetry, common fate
"open http://www.infovis-wiki.net/index.php?title=Gestalt_Laws".unixCmd
*/
// ================= FEATURE EXTRACTION IN SUPERCOLLIDER =================
// There are really a lot of UGens that analyze signals in SC. You would most commonly use those for audio, but they can be quite effective for any kind of incoming signal.
/* These examples are very basic, but you can refer to:
- class example 2.1_[Server-language communication] for receiving analysis data in sclang, and
- class example 6.2_[Mapping] about mapping strategies
*/
// ====== AMPLITUDE ======
// ------ Examples --
// The most fundamental UGen is Amplitude:
// • continuous:
(
a = CtkSynthDef(\ampTest, {arg inbus, attack, decay;
var amp, src;
src = WhiteNoise.ar(0.5);
amp = Amplitude.ar(In.ar(inbus), attack, decay);
amp.poll;
Out.ar(0, src * amp);
});
)
// try out a few different configuration of the analysis UGen
b = a.new.inbus_(s.options.numInputBusChannels).attack_(0.01).decay_(0.01).play;
b.free;
b = a.new.inbus_(s.options.numInputBusChannels).attack_(0.1).decay_(1).play;
b.free;
b = a.new.inbus_(s.options.numInputBusChannels).attack_(1.0).decay_(1).play;
b.free;
// • triggers:
// You can get amplitude data into sclang, and do something with it.
/* But:
- use Trig1 to limit the nr of data you get!
- allow yourself to set the trigger threshold and Trig1 'silent' duration from outside
*/
// This tracks incoming audio from your soundcard - beware of feedback
(
a = CtkSynthDef(\ampTest, {arg inbus, attack, decay, id, thresh, tdur;
var amp;
amp = Amplitude.ar(In.ar(inbus), attack, decay);
SendTrig.ar(Trig1.ar(amp > thresh, tdur), id, amp);
});
c = CtkSynthDef(\sin, {arg outbus = 0, freq = 220;
Out.ar(0, SinOsc.ar(440, 0, XLine.kr(0.1, 0.0001, 1, doneAction: 2)))
});
o = OSCresponderNode(s.addr, '/tr', {arg time, resp, msg;
[time, resp, msg].postln;
c.new.play
}).add;
)
b = a.new.inbus_(s.options.numInputBusChannels).attack_(0.01).decay_(0.01).id_(100).thresh_(0.15).tdur_(1).play;
o.remove;
b.free;
// ------ A list of AMPLITUDE tracking UGens in SC --
// >> Amplitude following
Amplitude // amplitude follower
PeakFollower // peak amp tracker
EnvDetect // amplitude follower
WAmp
// >> Amplitude minimum/maximum
Peak
DetectSilence
// >> Amplitude utilities
// pshychoacoustic utilities
AmpComp / AmpCompA
Loudness // get loudness in sones
// >> Signal operations:
Compander
Limiter
Normalizer // normalize to nominal level
// ====== PITCH ======
// ------ Examples --
// These examples use the incoming audio from your soundcard - beware of feedback
// • continuous:
// Using the Pitch UGen: pretty good!
(
a = CtkSynthDef(\pitch, {arg inbus, high = 1000, low = 60;
var in, freq, hasFreq;
in = In.ar(inbus, 1);
#freq, hasFreq = Pitch.kr(in, high, low, high);
freq.poll;
Out.ar(0, SinOsc.ar(freq * 4, 0, 0.1) * hasFreq)
});
)
b = a.new.inbus_(s.options.numOutputBusChannels).play;
b.free;
// The Tartini UGen is a more expensive, but is also better
(
a = CtkSynthDef(\pitch, {arg inbus;
var in, freq, hasFreq;
in = In.ar(inbus, 1);
#freq, hasFreq = Tartini.kr(in);
freq.poll;
Out.ar(0, SinOsc.ar(freq * 4, 0, 0.1))
});
)
b = a.new.inbus_(s.options.numOutputBusChannels).play;
b.free;
// • triggers:
// Triggering is much trickier with pitch, as precise pitch is close to impossible, so better to look for a specific region/register.
// This example will provide a trigger if the incoming audio is between 1500-1700Hz
(
a = CtkSynthDef(\pitch, {arg inbus, low = 1500, high = 1700, id, tdur = 0.5;
var in, freq, hasFreq, trig1, trig2, trig;
in = In.ar(inbus, 1);
#freq, hasFreq = Tartini.kr(in);
freq.poll;
trig1 = freq > low;
trig2 = freq < high;
trig = Trig1.kr((trig1 + trig2 - 1).poll, tdur);
SendTrig.kr(trig, id, freq);
});
b = a.new.inbus_(s.options.numOutputBusChannels).id_(100).play;
z = CtkSynthDef(\test, {arg freq;
Out.ar(0, SinOsc.ar(freq, 0, XLine.kr(0.1, 0.0001, 1, doneAction: 2)))
});
o = OSCresponderNode(s.addr, '/tr', {arg time, resp, msg;
[time, resp, msg].postln;
z.new.freq_(msg[3] * 0.5).play;
}).add;
)
b.free;
o.remove; // don't forget to remove the responder
// ------ A list of PITCH tracking UGens in SC --
Pitch
Tartini
ZeroCrossing
// >> more/advanced/experimental (ie: use at your own risk):
KeyTrack
// ====== TIME-RELATED (ONSETS, ETC) ======
// ------ Examples --
// There are a few different strategies for finding onsets. You can use some of the pre-cooked classes, or devise your own methods:
// These examples use the incoming audio from your soundcard - beware of feedback
// comparing a signal with a delayed version of itself (an example by Josh Parmenter):
(
a = CtkSynthDef(\ampTest, {arg inbus, level = 0.5, attack, decay, id;
var amp, src, del, diff;
src = In.ar(inbus, 1);
amp = Amplitude.ar(src, attack, decay);
del = DelayN.ar(amp, 0.01, 0.01);
diff = (amp / del.max(0.001)).ampdb *
EnvGen.kr(Env([0, 0, 1], [0.1, 0.1])).poll;
SendTrig.ar(Trig1.ar(diff > level, 1), id, diff);
});
z = CtkSynthDef(\test, {
Out.ar(0, SinOsc.ar(440, 0, XLine.kr(0.1, 0.0001, 1, doneAction: 2)))
});
o = OSCresponderNode(s.addr, '/tr', {arg time, resp, msg;
[time, resp, msg].postln;
// now - actually do something once the trigger happens
z.new.play;
}).add;
)
b = a.new.inbus_(s.options.numOutputBusChannels).level_(36).attack_(0.01).decay_(0.01).id_(100).play;
b.level_(12); // level is in db
o.remove;
b.free;
// or use the Slope UGen instead:
(
a = CtkSynthDef(\ampTest, {arg inbus, level = 0.5, attack, decay, id;
var amp, src, slope;
src = In.ar(inbus, 1);
amp = Amplitude.ar(src, attack, decay);
slope = Slope.ar(amp);
SendTrig.ar(Trig1.ar(slope > level, 1), id, slope);
});
z = CtkSynthDef(\test, {
Out.ar(0, SinOsc.ar(440, 0, XLine.kr(0.1, 0.0001, 1, doneAction: 2)))
});
o = OSCresponderNode(s.addr, '/tr', {arg time, resp, msg;
[time, resp, msg].postln;
// now - actually do something once the trigger happens
z.new.play;
}).add;
)
b = a.new.inbus_(s.options.numOutputBusChannels).level_(0.2).attack_(0.01).decay_(0.01).id_(100).play;
b.level_(200); // angle?
o.remove;
b.free;
// ------ A list of TIME (onset, rhythm, etc) tracking UGens in SC --
Onsets
Coyote
// >> more/advanced/experimental (ie: use at your own risk):
BeatTrack
BeatTrack2
AutoTrack
OnsetDS / XiiOnsets // no help file
SLOnset
Concat
// ====== TIMBRE ======
// There is a big variety of UGens that you can use for tracking characteristics on the spectrum of a signal in SC.
// ------ Examples --
// For example, SpecFlatness tells you how noisy a signal is (noise = 1, sine = 0)
(
f = Buffer.alloc(s,2048,1); // a buffer for the FFT to use
a = CtkSynthDef(\pitch, {arg inbus;
var in, freq, chain, flat;
in = In.ar(inbus, 1);
chain = FFT(f, in);
flat = SpecFlatness.kr(chain).poll;
flat = flat ** 3;
flat.poll;
Out.ar(0, BPF.ar(WhiteNoise.ar(0.5), 400, flat.max(0.0001)));
});
)
b = a.new.inbus_(s.options.numOutputBusChannels).play;
b.free;
// FFTPeak finds the bin with the strongest magnitude and outputs its frequency and magnitude:
(
f = Buffer.alloc(s, 2048, 1); // a buffer for the FFT to use
a = CtkSynthDef(\pitch, {arg inbus;
var in, chain, freq, mag;
in = In.ar(inbus, 1);
chain = FFT(f, in);
# freq, mag = FFTPeak.kr(chain).poll;
//peak = flat ** 3;
//flat.poll;
Out.ar(0, SinOsc.ar(freq, 0, mag * 0.001));
});
)
b = a.new.inbus_(s.options.numOutputBusChannels).play;
b.free;
// ------ A list of SPECTRAL FEATURE tracking UGens in SC --
SpecCentroid
SpecFlatness
SpecPcile
FFTCrest
FFTComplexDev
FFTFlux
FFTPeak
FFTRumble
FFTSlope
FFTSpread
FFTSubbandFlatness / FFTSubbandFlux / FFTSubbandPower
Goertzel // detect presence of a frequency
// You can also use filters to divide a signal into different ranges, and then detect energy for example in it
// >> more/advanced/experimental (ie: use at your own risk):
PV_CommonMag
MFCC
FrameCompare
Qitch
CQ_Diff
// These don't spit data, but re-synthesize for you:
PV_NoiseSynthF
PV_PartialSynthF
// ====== STATISTICAL UTILITIES ======
// It can be very useful, after analyzing a signal, to make a statistical analysis and get a better idea about its behavior
AverageOutput
TrigAvg
Max
RunningMax
RunningMin
RunningSum
Slope
Crest
StatUGens // PseudoUGens, built on RunningSum UGen; no help-file for the individual UGens
DynamicScaleUGen
FluctuationUGen
KurtosisUGen
MaxDynScaleUGen
MinDynScaleUGen
SkewUGen
StdDevUGen
// ====== COMBINING FEATURES ======
// An example combining onset, amplitude and pitch analysis (by Josh P)
// This examples use the incoming audio from your soundcard - beware of feedback
// Sing or whistle on the mic:
(
var freq, amp;
/* combining triggers - setting variable in the lang */
a = CtkSynthDef(\pitch, {arg inbus, attack, decay, id1, id2, id3,
level = 6;
var in, amp, del, diff, freq, hasFreq, trig1, trig2, trig;
in = In.ar(inbus, 1);
amp = Amplitude.ar(in, attack, decay);
del = DelayN.ar(amp, 0.01, 0.01);
// delays the trigger itself
diff = DelayN.ar((amp / del.max(0.001)).ampdb *
EnvGen.kr(Env([0, 0, 1], [0.1, 0.1])), 0.025, 0.025);
trig = Trig1.ar(diff > level, 1); // for amplitude
SendTrig.ar(trig, id1, RunningSum.kr(amp, 40)); // output the peak amplitude of the analysis
// send another trig with id2 for freq
#freq, hasFreq = Pitch.kr(in);
SendTrig.ar(trig, id2, freq);
// finally! send a last trigger with a slight delay, to ensure the
// variables for the above two triggers have been set
SendTrig.ar(DelayN.ar(trig, 0.01, 0.01), id3, freq);
});
b = a.new.inbus_(s.options.numOutputBusChannels).level_(50)
.id1_(100).id2_(101).id3_(102).play;
z = CtkSynthDef(\test, {arg freq, amp;
Out.ar(1, SinOsc.ar(freq, 0, amp * XLine.kr(0.1, 0.0001, 4, doneAction: 2)))
});
o = OSCresponderNode(nil, '/tr', {arg time, resp, msg;
case
{msg[2] == 100}
{amp = msg[3]; ("Amp is: "++amp).postln;}
{msg[2] == 101}
{freq = msg[3]; ("Freq is: "++freq).postln;}
{msg[2] == 102}
{
"Go!".postln;
// now - actually do something once the trigger happens
10.do({arg i;
z.new(0 + (i * 0.1.rand))
.freq_(freq * ((i + 1 * 2))).amp_(amp * 0.1).play;
})
}
}).add;
)
b.level_(12);
b.free;
o.remove;